import localization
import locale
import blue
import re
import copy
import time
propertyHandlersInitialized = False
propertyHandlers = {'characters': 'CharacterPropertyHandler',
 'items': 'ItemPropertyHandler',
 'locations': 'LocationPropertyHandler',
 'numericQuantities': 'NumericQuantityPropertyHandler'}
dateTimeQuantityMap = {('year', 'second'): 'UI/Generic/DateTimeQuantity/YearToSecond',
 ('year', 'minute'): 'UI/Generic/DateTimeQuantity/YearToMinute',
 ('year', 'hour'): 'UI/Generic/DateTimeQuantity/YearToHour',
 ('year', 'day'): 'UI/Generic/DateTimeQuantity/YearToDay',
 ('year', 'month'): 'UI/Generic/DateTimeQuantity/YearToMonth',
 ('year', 'year'): 'UI/Generic/DateTimeQuantity/Year',
 ('month', 'second'): 'UI/Generic/DateTimeQuantity/MonthToSecond',
 ('month', 'minute'): 'UI/Generic/DateTimeQuantity/MonthToMinute',
 ('month', 'hour'): 'UI/Generic/DateTimeQuantity/MonthToHour',
 ('month', 'day'): 'UI/Generic/DateTimeQuantity/MonthToDay',
 ('month', 'month'): 'UI/Generic/DateTimeQuantity/Month',
 ('day', 'second'): 'UI/Generic/DateTimeQuantity/DayToSecond',
 ('day', 'minute'): 'UI/Generic/DateTimeQuantity/DayToMinute',
 ('day', 'hour'): 'UI/Generic/DateTimeQuantity/DayToHour',
 ('day', 'day'): 'UI/Generic/DateTimeQuantity/Day',
 ('hour', 'second'): 'UI/Generic/DateTimeQuantity/HourToSecond',
 ('hour', 'minute'): 'UI/Generic/DateTimeQuantity/HourToMinute',
 ('hour', 'hour'): 'UI/Generic/DateTimeQuantity/Hour',
 ('minute', 'second'): 'UI/Generic/DateTimeQuantity/MinuteToSecond',
 ('minute', 'minute'): 'UI/Generic/DateTimeQuantity/Minute',
 ('second', 'second'): 'UI/Generic/DateTimeQuantity/Second'}
writtenDateTimeQuantityMap = {('year', 'second'): 'UI/Generic/WrittenDateTimeQuantity/YearToSecond',
 ('year', 'minute'): 'UI/Generic/WrittenDateTimeQuantity/YearToMinute',
 ('year', 'hour'): 'UI/Generic/WrittenDateTimeQuantity/YearToHour',
 ('year', 'day'): 'UI/Generic/WrittenDateTimeQuantity/YearToDay',
 ('year', 'month'): 'UI/Generic/WrittenDateTimeQuantity/YearToMonth',
 ('year', 'year'): 'UI/Generic/WrittenDateTimeQuantity/Year',
 ('month', 'second'): 'UI/Generic/WrittenDateTimeQuantity/MonthToSecond',
 ('month', 'minute'): 'UI/Generic/WrittenDateTimeQuantity/MonthToMinute',
 ('month', 'hour'): 'UI/Generic/WrittenDateTimeQuantity/MonthToHour',
 ('month', 'day'): 'UI/Generic/WrittenDateTimeQuantity/MonthToDay',
 ('month', 'month'): 'UI/Generic/WrittenDateTimeQuantity/Month',
 ('day', 'second'): 'UI/Generic/WrittenDateTimeQuantity/DayToSecond',
 ('day', 'minute'): 'UI/Generic/WrittenDateTimeQuantity/DayToMinute',
 ('day', 'hour'): 'UI/Generic/WrittenDateTimeQuantity/DayToHour',
 ('day', 'day'): 'UI/Generic/WrittenDateTimeQuantity/Day',
 ('hour', 'second'): 'UI/Generic/WrittenDateTimeQuantity/HourToSecond',
 ('hour', 'minute'): 'UI/Generic/WrittenDateTimeQuantity/HourToMinute',
 ('hour', 'hour'): 'UI/Generic/WrittenDateTimeQuantity/Hour',
 ('minute', 'second'): 'UI/Generic/WrittenDateTimeQuantity/MinuteToSecond',
 ('minute', 'minute'): 'UI/Generic/WrittenDateTimeQuantity/Minute',
 ('second', 'second'): 'UI/Generic/WrittenDateTimeQuantity/Second'}
smallWrittenDateTimeQuantityMap = {'year': 'UI/Generic/WrittenDateTimeQuantity/LessThanOneYear',
 'month': 'UI/Generic/WrittenDateTimeQuantity/LessThanOneMonth',
 'day': 'UI/Generic/WrittenDateTimeQuantity/LessThanOneDay',
 'hour': 'UI/Generic/WrittenDateTimeQuantity/LessThanOneHour',
 'minute': 'UI/Generic/WrittenDateTimeQuantity/LessThanOneMinute',
 'second': 'UI/Generic/WrittenDateTimeQuantity/LessThanOneSecond'}
dateTimeMap = {('year', 'second'): 'UI/Generic/DateTime/DateTime',
 ('year', 'day'): 'UI/Generic/DateTime/Date',
 ('hour', 'second'): 'UI/Generic/DateTime/Time'}
messageTagCache = {}

def _RegisterPropertyHandlers():
    global propertyHandlersInitialized
    import __builtin__
    while not hasattr(__builtin__, 'sm'):
        blue.synchro.Yield()

    for (variableName, handlerClassName,) in propertyHandlers.iteritems():
        if hasattr(localization, str(handlerClassName)):
            localization.LogInfo("Mapping variable type '", variableName, "' to property handler 'localization.", handlerClassName, "'.")
            propertyHandlers[variableName] = getattr(localization, handlerClassName)().GetProperty
        else:
            localization.LogInfo("Could not find specified property handler for variable type '", variableName, "' (localization.", handlerClassName, '). Mapping to debug property handler.')
            propertyHandlers[variableName] = _UndefinedPropertyHandlerFactory(variableName)

    propertyHandlersInitialized = True



def _Parse(sourceText, messageID, languageID, **kwargs):
    if (messageID and (messageID in messageTagCache)):
        tags = copy.deepcopy(messageTagCache[messageID])
    else:
        tags = _Tokenize(sourceText)
        messageTagCache[messageID] = copy.deepcopy(tags)
    if not tags:
        return sourceText
    else:
        for variableType in kwargs:
            if (variableType not in VARIABLE_TYPE_DATA):
                localization.LogError('Error in messageID', messageID, ' - Unexpected keyword argument: ', variableType, ' -- Source string:', sourceText)
                continue
            for variableName in kwargs[variableType]:
                for (tagName, tag,) in tags.iteritems():
                    if (tag['variableName'] == variableName):
                        if tag['variableType']:
                            localization.LogError('Error in messageID ', messageID, " - Variable '", variableName, "'' has already been defined. Source string:", sourceText, ' - Source parameters:', kwargs)
                            return sourceText
                        if (len(set(tag['args']).intersection(set(VARIABLE_TYPE_DATA[variableType]['args']))) < len(tag['args'])):
                            localization.LogError('Error in messageID ', messageID, " - Variable '", variableName, "' (type: ", variableType, ') contains an modifier not available for this type. Source string: ', sourceText, ' - Modifiers:', tag['args'])
                            return sourceText
                        if (len(set(tag['kwargs']).intersection(set(VARIABLE_TYPE_DATA[variableType]['kwargs']))) < len(tag['kwargs'])):
                            localization.LogError('Error in messageID ', messageID, " - Variable '", variableName, "' (type: ", variableType, ') contains a keyword modifier not available for this type. Source string: ', sourceText, ' - Modifiers:', tag['kwargs'])
                            return sourceText
                        tag['variableType'] = variableType
                        tag['variableValue'] = kwargs[variableType][variableName]



        parsedText = sourceText
        for (tagName, tag,) in tags.iteritems():
            if tag['propertyName']:
                tag['propertyValue'] = _GetPropertyValue(tag['variableType'], tag['propertyName'], tag['variableValue'], languageID)
            if tag['isConditional']:
                replaceText = _ProcessConditional(tagName, tag, languageID)
                if replaceText:
                    parsedText = parsedText.replace(tagName, replaceText)
            else:
                parsedText = VARIABLE_TYPE_DATA[variableType]['handlerFunction'](parsedText, tagName, tag)

        return parsedText



def _CharacterHandler(originalText, tag, tagData):
    result = _ApplyModifiers(unicode(tagData['propertyValue']), tagData)
    return originalText.replace(tag, result)



def _ItemHandler(originalText, tag, tagData):
    result = _ApplyModifiers(unicode(tagData['propertyValue']), tagData)
    return originalText.replace(tag, result)



def _LocationHandler(originalText, tag, tagData):
    result = _ApplyModifiers(unicode(tagData['propertyValue']), tagData)
    return originalText.replace(tag, result)



def _CharacterListHandler(originalText, tag, tagData):
    result = _ApplyModifiers(unicode(tagData['propertyValue']), tagData)
    return originalText.replace(tag, result)



def _MessageHandler(originalText, tag, tagData):
    result = _ApplyModifiers(unicode(tagData['variableValue']), tagData)
    return originalText.replace(tag, result)



def _DateTimeHandler(originalText, tag, tagData):
    value = tagData['variableValue']
    try:
        fromMark = tagData['kwargs']['from']
        toMark = tagData['kwargs']['to']
        formatString = dateTimeMap[(fromMark, toMark)]
    except KeyError, e:
        localization.LogError("The to/from pair '", tagData['kwargs'].get('from', None), ', ', tagData['kwargs'].get('to', None), "' is not a valid DateTime combination.  Source string: ", originalText)
        return originalText
    if isinstance(value, long):
        (year, month, weekday, day, hour, minute, second, msec,) = blue.os.GetTimeParts(value)
        day_of_year = 1
        is_daylight_savings = -1
        value = (year,
         month,
         day,
         hour,
         minute,
         second,
         weekday,
         day_of_year,
         is_daylight_savings)
    elif not isinstance(value, (time.struct_time, tuple)):
        localization.LogError('formattedDateTime only accepts blue time, struct_time, or tuples as values, but we recieved a ', type(value).__name__, '.  Source string: ', originalText)
        return originalText
    dateTimeString = localization.GetByLabel(formatString, formattedDateTimes={'datetime': value})
    result = _ApplyModifiers(unicode(dateTimeString), tagData)
    return originalText.replace(tag, result)



def _FormattedDateTimeHandler(originalText, tag, tagData):
    value = tagData['variableValue']
    try:
        formatString = tagData['kwargs']['format']
    except KeyError, e:
        return originalText
    if isinstance(value, long):
        (year, month, weekday, day, hour, minute, second, msec,) = blue.os.GetTimeParts(value)
        day_of_year = 1
        is_daylight_savings = -1
        value = (year,
         month,
         day,
         hour,
         minute,
         second,
         weekday,
         day_of_year,
         is_daylight_savings)
    elif not isinstance(value, (time.struct_time, tuple)):
        localization.LogError('formattedDateTime only accepts blue time, struct_time, or tuples as values, but we recieved a ', type(value).__name__, '.  Source string: ', originalText)
        return originalText
    dateTimeString = time.strftime(formatString, value)
    result = _ApplyModifiers(unicode(dateTimeString), tagData)
    return originalText.replace(tag, result)



def _DateTimeQuantityHandler(originalText, tag, tagData):
    value = tagData['variableValue']
    try:
        fromMark = tagData['kwargs']['from']
        toMark = tagData['kwargs']['to']
        if ('writtenForm' in tagData['args']):
            dateTimeQuantityLabel = writtenDateTimeQuantityMap[(fromMark, toMark)]
        else:
            dateTimeQuantityLabel = dateTimeQuantityMap[(fromMark, toMark)]
    except KeyError, e:
        localization.LogError("The to/from pair '", tagData['kwargs'].get('from', None), ', ', tagData['kwargs'].get('to', None), "' is not a valid DateTimeQuantity combination.  Source string: ", originalText)
        return originalText
    else:
        if isinstance(value, long):
            (year, month, weekday, day, hour, minute, second, msec,) = blue.os.GetTimeParts(value)
            year -= 1601
            month -= 1
            day -= 1
            day_of_year = 1
            is_daylight_savings = -1
        elif isinstance(value, (time.struct_time, tuple)):
            (year, month, day, hour, minute, second, weekday, day_of_year, is_daylight_savings,) = value
        else:
            localization.LogError('dateTimeQuantity only accepts blue time, struct_time, or tuples as values, but we recieved a ', type(value).__name__, '.  Source string: ', originalText)
            return originalText
        if (fromMark != 'year'):
            month += (year * 12)
            year = 0
            if (fromMark != 'month'):
                day += (month * 30)
                month = 0
                if (fromMark != 'day'):
                    hour += (day * 24)
                    day = 0
                    if (fromMark != 'hour'):
                        minute += (hour * 60)
                        hour = 0
                        if (fromMark != 'minute'):
                            second += (minute * 60)
                            minute = 0
        timeDict = {'year': year,
         'month': month,
         'day': day,
         'hour': hour,
         'minute': minute,
         'second': second}
        smallAmountOfTimeRemaining = None
        for timePart in ('year', 'month', 'day', 'hour', 'minute', 'second'):
            if (timeDict[timePart] != 0):
                if (smallAmountOfTimeRemaining == False):
                    smallAmountOfTimeRemaining = True
                break
            if ((fromMark == toMark) and (timePart == fromMark)):
                smallAmountOfTimeRemaining = False

    if smallAmountOfTimeRemaining:
        dateTimeQuantityLabel = smallWrittenDateTimeQuantityMap[fromMark]
    dateTimeString = localization.GetByLabel(dateTimeQuantityLabel, numericQuantities={'years': year,
     'months': month,
     'days': day,
     'hours': hour,
     'minutes': minute,
     'seconds': second})
    result = _ApplyModifiers(unicode(dateTimeString), tagData)
    return originalText.replace(tag, result)



def _NumericQuantityHandler(originalText, tag, tagData):
    value = tagData['variableValue']
    useGrouping = ('useGrouping' in tagData['args'])
    decimalPlaces = (int(tagData['kwargs']['decimalPlaces']) if ('decimalPlaces' in tagData['kwargs']) else None)
    leadingZeroes = (int(tagData['kwargs']['leadingZeroes']) if ('leadingZeroes' in tagData['kwargs']) else None)
    if decimalPlaces:
        formatString = ''.join(('%.',
         str(decimalPlaces),
         'f'))
        output = locale.format(formatString, value, grouping=useGrouping)
    elif leadingZeroes:
        formatString = ''.join(('%0',
         str(leadingZeroes),
         'd'))
        output = locale.format(formatString, value, grouping=useGrouping)
    elif isinstance(value, int):
        output = locale.format('%d', value, grouping=useGrouping)
    else:
        output = locale.format('%f', value, grouping=useGrouping)
    return originalText.replace(tag, output)



def _UndefinedHandler(originalText, tag, tagData):
    result = _ApplyModifiers(str(tagData['variableValue']), tagData)
    return originalText.replace(tag, result)



def _ApplyModifiers(message, tagData):
    if ('showInfolink' in tagData['args']):
        pass
    if ('capitalize' in tagData['args']):
        return message.capitalize()
    else:
        if ('uppercase' in tagData['args']):
            return message.upper()
        if ('lowercase' in tagData['args']):
            return message.lower()
        if ('titlecase' in tagData['args']):
            return message.title()
        return message



def _Tokenize(sourceText):
    sourceText = sourceText.replace('\r\n', ' ').replace('\n', ' ')
    tags = dict([ (each, None) for each in re.findall('(\\{(?!\\{).*?\\})', sourceText) ])
    for tag in tags.keys()[:]:
        originalTag = tag
        tag = tag[1:-1]
        if not len(tag.strip()):
            del tags[originalTag]
            continue
        tokens = re.split('(""|"[^"]+"|->|[.,=])', tag)
        tokens = [ token.strip() for token in tokens if token ]
        token = tokens.pop(0)
        if not _IsValidVariableName(token):
            message = (((((("Invalid variable token '" + token) + "' in tag '") + originalTag) + "' in string '") + sourceText) + "'")
            localization.LogError(message)
            del tags[originalTag]
            continue
        variableName = token
        propertyName = None
        tagArgs = []
        tagKwargs = {}
        isConditional = False
        conditionalValues = []
        if (tokens and (tokens[0] == '.')):
            propertyName = tokens.pop(0)
            while (tokens and ((tokens[0] != ',') and (tokens[0] != '->'))):
                propertyName += tokens.pop(0)

            if not _IsValidAttributeName(propertyName):
                message = (((((("Invalid property token '" + propertyName) + "' in tag '") + originalTag) + "' in string '") + sourceText) + "'")
                localization.LogError(message)
                del tags[originalTag]
                continue
            propertyName = propertyName[1:]
        parseError = False
        if (tokens and (tokens[0] == ',')):
            while (tokens and (tokens[0] == ',')):
                tokens.pop(0)
                arg = tokens.pop(0)
                if (tokens and (tokens[0] == '=')):
                    tokens.pop(0)
                    if (tokens and tokens[0].isalnum()):
                        tagKwargs[arg] = tokens.pop(0)
                    elif (tokens and (tokens[0].startswith('"') and tokens[0].endswith('"'))):
                        tagKwargs[arg] = tokens.pop(0)[1:-1]
                    else:
                        parseError = True
                        message = ((str(tokens[0]) + 'is not a valid keyword argument value.  Source tag: ') + str(originalTag))
                        localization.LogError(message)
                        break
                else:
                    tagArgs.append(arg)

            if parseError:
                del tags[originalTag]
                continue
        elif (tokens and (tokens[0] == '->')):
            validConditionalProperties = ('gender', 'genders', 'quantity')
            if (propertyName not in validConditionalProperties):
                message = ((((str(variableName) + '.') + str(propertyName)) + ' is not a valid property; it cannot be used in conditional statements.  Valid properties are: ') + str(validConditionalProperties))
                localization.LogError(message)
                del tags[originalTag]
                continue
            else:
                isConditional = True
                tokens.pop(0)
                while (tokens and ((len(tokens[0]) >= 2) and (tokens[0].startswith('"') and tokens[0].endswith('"')))):
                    conditionalValues.append(tokens[0][1:-1])
                    tokens.pop(0)
                    if (tokens and (tokens[0] == ',')):
                        tokens.pop(0)
                    else:
                        if tokens:
                            parseError = True
                            message = ((("Unexpected token '" + str(tokens[0])) + "' encountered. Source tag: ") + str(originalTag))
                            localization.LogError(message)

                if parseError:
                    del tags[originalTag]
                    continue
        tags[originalTag] = {'variableType': None,
         'variableName': variableName,
         'variableValue': None,
         'propertyName': propertyName,
         'propertyValue': None,
         'isConditional': isConditional,
         'conditionalValues': conditionalValues,
         'args': tagArgs,
         'kwargs': tagKwargs}

    return tags



def _IsValidVariableName(string):
    results = re.findall('[a-zA-Z_]+\\w?', string)
    return (results and (results[0] == string))



def _IsValidAttributeName(string):
    results = re.findall('[\\.[a-zA-Z_]+\\w*]*', string)
    return (results and (results[0] == string))



def _GetPropertyValue(variableType, propertyName, value, languageID):
    if not propertyHandlersInitialized:
        _RegisterPropertyHandlers()
    return propertyHandlers[variableType](propertyName, value, languageID)



def _ProcessConditional(tagName, tag, languageID):
    try:
        if (tag['propertyName'] == 'gender'):
            if (tag['propertyValue'] >= len(tag['conditionalValues'])):
                localization.LogError('Insufficient parameters for gender conditional statement; source tag:', tagName)
                return tagName
            else:
                return tag['conditionalValues'][(tag['propertyValue'] % len(tag['conditionalValues']))]
            if (tag['propertyName'] == 'quantity'):
                quantityCategory = _GetQuantityCategory(tag['propertyValue'], languageID)
                if (quantityCategory >= len(tag['conditionalValues'])):
                    localization.LogError('Insufficient parameters for quantity conditional statement; quantity category is ', quantityCategory, ', but only ', len(tag['conditionalValues']), ' values were provided in the conditional statement.  Source tag:', tagName)
                    return tagName
                return tag['conditionalValues'][quantityCategory]
            localization.LogError("'", str(tag['propertyName']), "' is not a valid property of '", str(tag['variableName']), "', or someone forgot to write code to handle the '", str(tag['propertyName']), "' property for the '", str(tag['variableType']), "' variable type.")
            return tagName
    except TypeError, e:
        localization.LogError("'", str(tag['propertyValue']), "' is not a valid value for '", str(tag['variableName']), '.', str(tag['propertyName']), "' property.")
        return tagName
    localization.LogError("Someone forgot to write code to handle the '", str(tag['propertyName']), "' property for the '", str(tag['variableType']), "' variable type.")
    return tagName



def _UndefinedPropertyHandlerFactory(variableName):
    return lambda propertyName, variableID, languageID: _UndefinedPropertyHandler(variableName, propertyName, variableID, languageID)



def _UndefinedPropertyHandler(variableName, propertyName, variableID, languageID):
    localization.LogError("No property handler defined for variables of type '", variableName, "' on ", boot.role, '.')
    return ((('?' + variableName) + '.') + propertyName)



def _GetQuantityCategory(quantity, languageID):
    try:
        return quantityCategoryMap[languageID](quantity)
    except KeyError, e:
        localization.LogError("No quantity category defined for language '", languageID, "'; defaulting to English rules.")
        return quantityCategoryMap['en-us'](quantity)



def _GetType1QuantityCategory(quantity):
    return 0



def _GetType2QuantityCategory(quantity):
    if ((quantity == 1) or (quantity == -1)):
        return 0
    else:
        return 1



def _GetType3QuantityCategory(quantity):
    if (quantity < 0):
        quantity = -quantity
    if ((quantity != 11) and ((quantity % 10) == 1)):
        return 0
    else:
        if (((quantity < 12) or (quantity > 14)) and 2 <= (quantity % 10) <= 4):
            return 1
        return 2


BASIC_TEXT_MODIFIERS = ['capitalize',
 'uppercase',
 'lowercase',
 'titlecase']
VARIABLE_TYPE_DATA = {'characters': {'args': (BASIC_TEXT_MODIFIERS + ['showInfolink']),
                'kwargs': [],
                'handlerFunction': _CharacterHandler},
 'items': {'args': (BASIC_TEXT_MODIFIERS + ['showInfolink']),
           'kwargs': [],
           'handlerFunction': _ItemHandler},
 'locations': {'args': (BASIC_TEXT_MODIFIERS + ['showInfolink']),
               'kwargs': [],
               'handlerFunction': _LocationHandler},
 'characterLists': {'args': [],
                    'kwargs': [],
                    'handlerFunction': _CharacterListHandler},
 'messages': {'args': BASIC_TEXT_MODIFIERS,
              'kwargs': [],
              'handlerFunction': _MessageHandler},
 'dateTimes': {'args': (BASIC_TEXT_MODIFIERS + ['showInfolink', 'showTimeZone']),
               'kwargs': ['to', 'from'],
               'handlerFunction': _DateTimeHandler},
 'formattedDateTimes': {'args': (BASIC_TEXT_MODIFIERS + ['showInfolink', 'showTimeZone']),
                        'kwargs': ['format'],
                        'handlerFunction': _FormattedDateTimeHandler},
 'dateTimeQuantities': {'args': (BASIC_TEXT_MODIFIERS + ['showInfolink', 'writtenForm']),
                        'kwargs': ['to', 'from'],
                        'handlerFunction': _DateTimeQuantityHandler},
 'numericQuantities': {'args': ['useGrouping'],
                       'kwargs': ['decimalPlaces', 'leadingZeroes'],
                       'handlerFunction': _NumericQuantityHandler},
 'undefined': {'args': BASIC_TEXT_MODIFIERS,
               'kwargs': [],
               'handlerFunction': _UndefinedHandler}}
quantityCategoryMap = {'en-us': _GetType2QuantityCategory,
 'ja': _GetType1QuantityCategory,
 'ru': _GetType3QuantityCategory,
 'P1': _GetType2QuantityCategory}
exports = {'localization._Parse': _Parse,
 'localization.messageTagCache': messageTagCache}

